//
// $Id: sprintf.as 8 2010-06-11 18:51:33Z karma@deadmoose.com $
//
// aspirin library - Taking some of the pain out of Actionscript development.
// Copyright (C) 2007-2010 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/ooo-aspirin/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

/*  sprintf(3) implementation in ActionScript 3.0.
 *
 *  Author:  Manish Jethani (manish.jethani@gmail.com)
 *  Date:    April 3, 2006
 *  Version: 0.1
 *
 *  Copyright (c) 2006 Manish Jethani
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 */
package com.threerings.util
{

/*  sprintf(3) implementation in ActionScript 3.0.
 *
 *  http://www.die.net/doc/linux/man/man3/sprintf.3.html
 *
 *  The following flags are supported: '#', '0', '-', '+'
 *
 *  Field widths are fully supported.  '*' is not supported.
 *
 *  Precision is supported except one difference from the standard: for an
 *  explicit precision of 0 and a result string of "0", the output is "0"
 *  instead of an empty string.
 *
 *  Length modifiers are not supported.
 *
 *  The following conversion specifiers are supported: 'd', 'i', 'o', 'u', 'x',
 *  'X', 'f', 'F', 'c', 's', '%'
 *
 *  Report bugs to manish.jethani@gmail.com
 */
public function sprintf(format:String, ... args):String
{
    var result:String = "";

    var length:int = format.length;
    for (var i:int = 0; i < length; i++)
    {
        var c:String = format.charAt(i);

        if (c == "%")
        {
            var next: *;
            var str: String;
            var pastFieldWidth:Boolean = false;
            var pastFlags:Boolean = false;

            var flagAlternateForm:Boolean = false;
            var flagZeroPad:Boolean = false;
            var flagLeftJustify:Boolean = false;
            var flagSpace:Boolean = false;
            var flagSign:Boolean = false;

            var fieldWidth:String = "";
            var precision:String = "";

            c = format.charAt(++i);

            while (c != "d"
                && c != "i"
                && c != "o"
                && c != "u"
                && c != "x"
                && c != "X"
                && c != "f"
                && c != "F"
                && c != "c"
                && c != "s"
                && c != "%")
            {
                if (!pastFlags)
                {
                    if (!flagAlternateForm && c == "#")
                        flagAlternateForm = true;
                    else if (!flagZeroPad && c == "0")
                        flagZeroPad = true;
                    else if (!flagLeftJustify && c == "-")
                        flagLeftJustify = true;
                    else if (!flagSpace && c == " ")
                        flagSpace = true;
                    else if (!flagSign && c == "+")
                        flagSign = true;
                    else
                        pastFlags = true;
                }

                if (!pastFieldWidth && c == ".")
                {
                    pastFlags = true;
                    pastFieldWidth = true;

                    c = format.charAt(++i);
                    continue;
                }

                if (pastFlags)
                {
                    if (!pastFieldWidth)
                        fieldWidth += c;
                    else
                        precision += c;
                }

                c = format.charAt(++i);
            }

            switch (c)
            {
            case "d":
            case "i":
                next = args.shift();
                str = String(Math.abs(int(next)));

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                if (int(next) < 0)
                    str = "-" + str;
                else if (flagSign && int(next) >= 0)
                    str = "+" + str;

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                result += str;
                break;

            case "o":
                next = args.shift();
                str = uint(next).toString(8);

                if (flagAlternateForm && str != "0")
                    str = "0" + str;

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                result += str;
                break;

            case "u":
                next = args.shift();
                str = uint(next).toString(10);

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                result += str;
                break;

            case "X":
                var capitalise:Boolean = true;
            case "x":
                next = args.shift();
                str = uint(next).toString(16);

                if (precision != "")
                    str = leftPad(str, int(precision), "0");

                var prepend:Boolean = flagAlternateForm && uint(next) != 0;

                if (fieldWidth != "" && !flagLeftJustify
                        && flagZeroPad && precision == "")
                    str = leftPad(str, prepend
                            ? int(fieldWidth) - 2 : int(fieldWidth), "0");

                if (prepend)
                    str = "0x" + str;

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                if (capitalise)
                    str = str.toUpperCase();

                result += str;
                break;

            case "f":
            case "F":
                next = args.shift();
                str = Math.abs(Number(next)).toFixed( precision != "" ?  int(precision) : 6 );

                if (int(next) < 0)
                    str = "-" + str;
                else if (flagSign && int(next) >= 0)
                    str = "+" + str;

                if (flagAlternateForm && str.indexOf(".") == -1)
                    str += ".";

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else if (flagZeroPad && precision == "")
                        str = leftPad(str, int(fieldWidth), "0");
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                result += str;
                break;

            case "c":
                next = args.shift();
                str = String.fromCharCode(int(next));

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                result += str;
                break;

            case "s":
                next = args.shift();
                str = String(next);

                if (precision != "")
                    str = str.substring(0, int(precision));

                if (fieldWidth != "")
                {
                    if (flagLeftJustify)
                        str = rightPad(str, int(fieldWidth));
                    else
                        str = leftPad(str, int(fieldWidth));
                }

                result += str;
                break;

            case "%":
                result += "%";
            }
        }
        else
        {
            result += c;
        }
    }

    return result;
}

}

// Private functions

function leftPad(source:String, targetLength:int, padChar:String = " "):String
{
    if (source.length < targetLength)
    {
        var padding:String = "";

        while (padding.length + source.length < targetLength)
            padding += padChar;

        return padding + source;
    }

    return source;
}

function rightPad(source:String, targetLength:int, padChar:String = " "):String
{
    while (source.length < targetLength)
        source += padChar;

    return source;
}
